/*
 * $Id: Boot.java 64 2009-04-17 14:18:44Z meyer@alphatec.de $
 * 
 *
 * Copyright 2009 DeltaDesk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package deltadesk.boot;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.java.plugin.ObjectFactory;
import org.java.plugin.PluginManager;
import org.java.plugin.PluginManager.PluginLocation;
import org.java.plugin.registry.IntegrityCheckReport;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.registry.IntegrityCheckReport.ReportItem;
import org.java.plugin.standard.StandardPluginLocation;

import deltadesk.io.DirectoryFileFilter;

/**
 * Boot launcher. Plugin loading and core startup.
 * 
 * @author Andreas Meyer
 * @version $Revision: 64 $
 */
public final class Boot {
	
	/** The main configuration file. */
	private static final String PROPERTIES_FILE = "boot.properties";
    
	/** Parameter for the plugin folder. */
    private static final String PARAM_PLUGINS_FOLDERS = "deltadesk.boot.pluginsFolders";

    /** The main plugin. */
    private static final String CORE_PLUGIN = "deltadesk.core";

    /** Logger for this class. */
    private static Log LOG = LogFactory.getLog(Boot.class);
    
    private final URL propertiesURL; 

    /** Core config. */
    private Properties props;

    /** The JPF plugin-manager. */
    private PluginManager pluginManager;
    
    
    /**
     * Construct the boot loader.
     * @throws Exception 
     */
    private Boot() throws Exception {
    	
    	propertiesURL = Boot.class.getClassLoader().getResource(PROPERTIES_FILE);

        // Load start-up configuration
        long startTime = System.currentTimeMillis();
    	if(!initConfig()) {

            System.err
					.println("Init failed. Core config not found. Aborting.\n");
            System.exit(1);
        }

        LOG.info("==================================================");
        LOG.info("DelatDesk BOOT");
        LOG.info("==================================================");
        pluginManager = ObjectFactory.newInstance().createManager();

        // load all plugins
        load();
        
        // dump plugin registry
        dumpRegistry();
        
        // check dependencies
        if(!checkDependencies()) {
        	LOG.fatal("Errors found in Registry. Aborting.\n");
            System.exit(1);
        }

        // boot the core plugin
        bootCore();

        // startup done =:B
        long endTime = System.currentTimeMillis();
        LOG.info("--------------------------------------------------");        
        LOG.info("System up and running. Startup took " + (endTime - startTime)
				+ "ms.");        
        LOG.info("==================================================");        
    }

    /**
     * Load and publish configuration
     * 
     * @throws IOException if some error occured loading the config file. 
     *  
     */
    private boolean initConfig() throws IOException {

        props = new Properties();
        try {
            
            final InputStream is = propertiesURL.openStream();
            props.load(is);
            is.close();
        } catch (final NullPointerException e) {
            return false;
        }
        return true;
    }

    /**
     * Load all plugins. Determine if we were launched as jar or from
     * filesystem.
     * 
     * @throws Exception
     */
    private void load() throws Exception {

        try {
        	
        	final String fullName = propertiesURL.getFile(); 
        	final int idx = fullName.indexOf(".jar");
        	if(idx >= 0) {
        		
        		final String baseFilename = fullName.substring(0, idx + 4);
        		final String jarFilename = URLDecoder.decode(new URL(
						baseFilename).getFile(), "UTF-8");
        		final JarFile jar = new JarFile(jarFilename);
        		LOG.info(" - Launched as jar: [YES][" + jar.getName() + "]");
        		
                // load plugins from jar
                loadFromJar(jar);
        	} else {
            	LOG.info(" - Launched as jar: [NO]");
                
                // load plugins from filesystem
                loadFromFileSystem();
        		
        	}
        } catch (final ZipException ze) {
        	ze.printStackTrace();
        	System.exit(1);
        }
    }

    /**
     * Dump plugin registry.
     */
    private void dumpRegistry() {

    	LOG.info("\nLoaded plugins:");
        for (final PluginDescriptor pd : pluginManager.getRegistry()
				.getPluginDescriptors()) {
			LOG.info("   [" + pd.getId() + "@" + pd.getVersion() + "]");
		} // end for
    }

    /**
     * Check all dependencies
     */
    private boolean checkDependencies() {
    	
        boolean status=true;
        
        LOG.info("\nCheck Registry:");
        
        final IntegrityCheckReport report = pluginManager.getRegistry()
				.checkIntegrity(pluginManager.getPathResolver());
        for(final ReportItem ri : report.getItems()) {

            IntegrityCheckReport.Error code = ri.getCode();
            if(code != IntegrityCheckReport.Error.NO_ERROR) {
            	
            	LOG.info("ERROR[" + code + "] " + ri.getMessage());
                if(code == IntegrityCheckReport.Error.UNSATISFIED_PREREQUISITE) {
					status = false;
				}
            } // end if
        } // end for

        LOG.info(" - Warnings: [" + report.countWarnings() + "]");
        LOG.info(" - Errors:   [" + report.countErrors() + "]");

        return status;
    }

    /**
     * Boot the core plugin
     * 
     * @throws Exception if some error occured loading the core plugin
     */
    private void bootCore() throws Exception {

        LOG.info("\nBooting core Plugin: [" + CORE_PLUGIN + "]");
        try {
        	
        	LOG.info("\nBooting:");
        	LOG.info("--------------------------------------------------");
            pluginManager.getPlugin(CORE_PLUGIN);
        } catch(final IllegalArgumentException e) {

        	LOG.fatal("Core Plugin not found. Aborting.");
            System.exit(1);
        }
    }

    /**
     * Load the plugins from jar.
     * 
     * @param jar
     * @throws Exception
     */
    private void loadFromJar(final JarFile jar) throws Exception {

        final StringTokenizer st = new StringTokenizer(props
				.getProperty(PARAM_PLUGINS_FOLDERS), ",", false);
        String pluginLocation="";
        if(st.hasMoreTokens()) {
            pluginLocation = new File(st.nextToken().trim()).getName();
        } else {
            return;
        }

        List<PluginLocation> pluginLocations = new LinkedList<PluginLocation>();

        Enumeration<JarEntry> e = jar.entries();
        while(e.hasMoreElements()) {
        	
            String nextEntry = e.nextElement().toString();
            if (nextEntry.startsWith(pluginLocation)
					&& nextEntry.endsWith("plugin.xml")) {

                final URL manifestUrl = new URL("jar:file:"+jar.getName()+"!/" + nextEntry);
                final URL contextUrl = new URL("jar:file:"+jar.getName()+"!/" + nextEntry.substring(0, nextEntry.lastIndexOf("/")+1));
                pluginLocations.add(new StandardPluginLocation(contextUrl,
						manifestUrl));
            } // end if
        } // end while
        
        pluginManager.publishPlugins(pluginLocations
				.toArray(new PluginLocation[pluginLocations.size()]));
    }

    /**
     * Internal helper method.
     * Load the plugins from filesystem.
     * 
     * @throws Exception if some error occured on loading the plgins
     */
    private void loadFromFileSystem() throws Exception {

        List<PluginLocation> pluginLocations = new LinkedList<PluginLocation>();

        // Prepare parameters to start plug-in manager
        final StringTokenizer st = new StringTokenizer(props.getProperty(PARAM_PLUGINS_FOLDERS), ",", false);
        while (st.hasMoreTokens()) {
            File folder = new File(st.nextToken().trim()).getCanonicalFile();
            
            LOG.info(" - Plugins located: [" + folder + "]");
            
            if (!folder.isDirectory()) {
                LOG.fatal("Plugins folder " + folder + " doesn't exist. Aborting.\n");
                System.exit(1);
            }
            
            File[] pluginFolders = folder.listFiles(new DirectoryFileFilter());
                for (int i = 0; i < pluginFolders.length; i++) {
                    File pluginFolder = pluginFolders[i];
                    File manifestFile = getManifestFile(pluginFolder);
                    if (manifestFile != null) {
                        
                    	final URL manifestUrl = manifestFile.toURL();
                        final URL contextUrl = pluginFolder.toURL();
                        pluginLocations.add(new StandardPluginLocation(contextUrl,
							manifestUrl));
                    } // end if
                } // end for
        } // end while
        
        pluginManager.publishPlugins(pluginLocations
				.toArray(new PluginLocation[pluginLocations.size()]));
    }

    
    /**
     * Look for the plugin-manifest for a given folder.
     * 
     * @param folder Folder to look for the manifest
     * @return Manifest file if found. null otherwise
     */
    private File getManifestFile(final File folder) {

        File result = new File(folder, "plugin.xml");
        if (result.isFile()) {
            return result;
        }
        result = new File(folder, "plugin-fragment.xml");
        if (result.isFile()) {
            return result;
        }
        return null;
    }

    
    /**
     * Call this method to start up program.
     * 
     * @param args no arguments expected
     * @throws Exception if any error occurred
     */
    public static void main(final String[] args) throws Exception {
        
    	new Boot();
    }

}
